<script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview This file defines a singleton which provides access to all data
 * that is available as soon as the page's resources are loaded (before DOM
 * content has finished loading). This data includes both localized strings and
 * any data that is important to have ready from a very early stage (e.g. things
 * that must be displayed right away).
 *
 * Note that loadTimeData is not guaranteed to be consistent between page
 * refreshes (https://crbug.com/740629) and should not contain values that might
 * change if the page is re-opened later.
 */

/**
 * @typedef {{
 *   substitutions: (Array<string>|undefined),
 *   attrs: (Object<function(Node, string):boolean>|undefined),
 *   tags: (Array<string>|undefined),
 * }}
 */
let SanitizeInnerHtmlOpts;

// eslint-disable-next-line no-var
/** @type {!LoadTimeData} */ var loadTimeData;

// Expose this type globally as a temporary work around until
// https://github.com/google/closure-compiler/issues/544 is fixed.
/** @constructor */
function LoadTimeData(){}

(function() {
    'use strict';

    LoadTimeData.prototype = {
        /**
         * Sets the backing object.
         *
         * Note that there is no getter for |data_| to discourage abuse of the form:
         *
         *     var value = loadTimeData.data()['key'];
         *
         * @param {Object} value The de-serialized page data.
         */
        set data(value) {
            expect(!this.data_, 'Re-setting data.');
            this.data_ = value;
        },

        /**
         * Returns a JsEvalContext for |data_|.
         * @returns {JsEvalContext}
         */
        createJsEvalContext: function() {
            return new JsEvalContext(this.data_);
        },

        /**
         * @param {string} id An ID of a value that might exist.
         * @return {boolean} True if |id| is a key in the dictionary.
         */
        valueExists: function(id) {
            return id in this.data_;
        },

        /**
         * Fetches a value, expecting that it exists.
         * @param {string} id The key that identifies the desired value.
         * @return {*} The corresponding value.
         */
        getValue: function(id) {
            expect(this.data_, 'No data. Did you remember to include strings.js?');
            const value = this.data_[id];
            expect(typeof value != 'undefined', 'Could not find value for ' + id);
            return value;
        },

        /**
         * As above, but also makes sure that the value is a string.
         * @param {string} id The key that identifies the desired string.
         * @return {string} The corresponding string value.
         */
        getString: function(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'string');
            return /** @type {string} */ (value);
        },

        /**
         * Returns a formatted localized string where $1 to $9 are replaced by the
         * second to the tenth argument.
         * @param {string} id The ID of the string we want.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {string} The formatted string.
         */
        getStringF: function(id, var_args) {
            const value = this.getString(id);
            if (!value) {
                return '';
            }

            const args = Array.prototype.slice.call(arguments);
            args[0] = value;
            return this.substituteString.apply(this, args);
        },

        /**
         * Make a string safe for use with with Polymer bindings that are
         * inner-h-t-m-l (or other innerHTML use).
         * @param {string} rawString The unsanitized string.
         * @param {SanitizeInnerHtmlOpts=} opts Optional additional allowed tags and
         *     attributes.
         * @return {string}
         */
        sanitizeInnerHtml: function(rawString, opts) {
            opts = opts || {};
            return parseHtmlSubset('<b>' + rawString + '</b>', opts.tags, opts.attrs)
                .firstChild.innerHTML;
        },

        /**
         * Returns a formatted localized string where $1 to $9 are replaced by the
         * second to the tenth argument. Any standalone $ signs must be escaped as
         * $$.
         * @param {string} label The label to substitute through.
         *     This is not an resource ID.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {string} The formatted string.
         */
        substituteString: function(label, var_args) {
            const varArgs = arguments;
            return label.replace(/\$(.|$|\n)/g, function(m) {
                assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
                return m == '$$' ? '$' : varArgs[m[1]];
            });
        },

        /**
         * Returns a formatted string where $1 to $9 are replaced by the second to
         * tenth argument, split apart into a list of pieces describing how the
         * substitution was performed. Any standalone $ signs must be escaped as $$.
         * @param {string} label A localized string to substitute through.
         *     This is not an resource ID.
         * @param {...(string|number)} var_args The extra values to include in the
         *     formatted output.
         * @return {!Array<!{value: string, arg: (null|string)}>} The formatted
         *     string pieces.
         */
        getSubstitutedStringPieces: function(label, var_args) {
            const varArgs = arguments;
            // Split the string by separately matching all occurrences of $1-9 and of
            // non $1-9 pieces.
            const pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
                []).map(function(p) {
                // Pieces that are not $1-9 should be returned after replacing $$
                // with $.
                if (!p.match(/^\$[1-9]$/)) {
                    assert(
                        (p.match(/\$/g) || []).length % 2 == 0,
                        'Unescaped $ found in localized string.');
                    return {value: p.replace(/\$\$/g, '$'), arg: null};
                }

                // Otherwise, return the substitution value.
                return {value: varArgs[p[1]], arg: p};
            });

            return pieces;
        },

        /**
         * As above, but also makes sure that the value is a boolean.
         * @param {string} id The key that identifies the desired boolean.
         * @return {boolean} The corresponding boolean value.
         */
        getBoolean: function(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'boolean');
            return /** @type {boolean} */ (value);
        },

        /**
         * As above, but also makes sure that the value is an integer.
         * @param {string} id The key that identifies the desired number.
         * @return {number} The corresponding number value.
         */
        getInteger: function(id) {
            const value = this.getValue(id);
            expectIsType(id, value, 'number');
            expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
            return /** @type {number} */ (value);
        },

        /**
         * Override values in loadTimeData with the values found in |replacements|.
         * @param {Object} replacements The dictionary object of keys to replace.
         */
        overrideValues: function(replacements) {
            expect(
                typeof replacements == 'object',
                'Replacements must be a dictionary object.');
            for (const key in replacements) {
                this.data_[key] = replacements[key];
            }
        }
    };

    /**
     * Checks condition, displays error message if expectation fails.
     * @param {*} condition The condition to check for truthiness.
     * @param {string} message The message to display if the check fails.
     */
    function expect(condition, message) {
        if (!condition) {
            console.error(
                'Unexpected condition on ' + document.location.href + ': ' + message);
        }
    }

    /**
     * Checks that the given value has the given type.
     * @param {string} id The id of the value (only used for error message).
     * @param {*} value The value to check the type on.
     * @param {string} type The type we expect |value| to be.
     */
    function expectIsType(id, value, type) {
        expect(
            typeof value == type, '[' + value + '] (' + id + ') is not a ' + type);
    }

    expect(!loadTimeData, 'should only include this file once');
    loadTimeData = new LoadTimeData;
})();
</script>